#pragma once 

#include <sys/shm.h>
#include <iostream>
#include <string>
#include <cstdio>

const int proj_id = 0;

#define ERR_EXIT(str)       \
    do                      \
    {                       \
        perror(str);        \
        exit(EXIT_FAILURE); \
    } while (false)         \

#define CREATER "creater"
#define USER "user"

class Shm
{
private:

    void _createOrGet(int flg)
    {
        _shmid = shmget(_key, _size, flg);

        if (_shmid < 0)
        {
            ERR_EXIT("shmget");
        }
        std::cout << "shmget success" << std::endl;
    }

    void create()
    {
        _createOrGet(IPC_CREAT | IPC_EXCL | 0666);
    }

    void get()
    {
        _createOrGet(IPC_CREAT);
    }

    void attach()
    {
        _start_mem = shmat(_shmid, nullptr, 0);

        if ((long long)_start_mem < 0)
        {
            ERR_EXIT("shmat");
        }
        std::cout << "shmat success" << std::endl;
    }

    void detach()
    {
        int ans = shmdt(_start_mem);

        if (ans < 0)
        {
            ERR_EXIT("shmdt");
        }

        std::cout << "shmdt success" << std::endl;
    }

    void destroy()
    {
        int ans = shmctl(_shmid, IPC_RMID, nullptr);

        if (ans < 0)
        {
            ERR_EXIT("shmctl");
        }
        std::cout << "shmctl success" << std::endl;
    }
    
public:

    Shm(const std::string& pathname, const std::string& usertype)
        :_shmid(0)
        ,_key(0)
        ,_size(4096)
        ,_start_mem(nullptr)
        ,_usertype(usertype)
    {
        _key = ftok(pathname.c_str(), proj_id);

        if (_key < 0)
        {
            ERR_EXIT("ftok");
        }

        if (_usertype == CREATER)
        {
            create();
        }
        else if (_usertype == USER)
        {
            get();
        }
        else
        {
            ERR_EXIT("usertype");
        }
        attach();
    }

    void* virtualAddr()
    {
        return _start_mem;
    }

    size_t size()
    {
        return _size;
    }

    ~Shm()
    {
        detach();

        if (_usertype == CREATER)
        {
            destroy();
        }
    }

private:

    key_t _key;
    int _shmid;
    size_t _size;
    void* _start_mem;
    std::string _usertype;
};
